<?php
namespace Kuoqi\Worker;
/**
 * Class Socket
 * @package Kuoqi
 */
class Socket{

    /**
     * 服务地址
     * @var string
     * User kuoqi
     * Datetime 2022/3/24 9:06
     * Remark
     */
	protected $server_address = '0.0.0.0';
    /**
     * 服务端口
     * @var string
     * User kuoqi
     * Datetime 2022/3/24 9:06
     * Remark
     */
    protected $server_port = '';
    /**
     * 当前是否有效
     * @var bool
     * User kuoqi
     * Datetime 2022/3/24 9:07
     * Remark
     */
    protected $is_valied = true;
    /**
     * 如果无效的时候展示的错误信息
     * @var string
     * User kuoqi
     * Datetime 2022/3/24 9:08
     * Remark
     */
    protected $errmsg = '';


    /**
     * 是否开启debug
     * @var bool
     * User kuoqi
     * Datetime 2022/3/24 9:34
     * Remark
     */
    protected $debug = true;

    /**
     * 最多可积压的客户端数量
     * @var int
     * User kuoqi
     * Datetime 2022/3/24 9:15
     * Remark
     */
    protected $backlog = 2;

    /**
     * 用于存储当前的服务socket列表
     * @var array
     * User kuoqi
     * Datetime 2022/3/24 9:46
     * Remark
     */
    protected $sockets = array();



    /**
     * 创建一个socket模块
     * Socket constructor.
     * @param $address
     * @param $port
     */
	public function __construct($address,$port){
		$this->server_address = $address;
		$this->server_port = $port;

		if(!($this->master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP))){
		    $this->errmsg = "socket_create() failed";
		    $this->is_valied = false;
        }
        if(!socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)){
            $this->errmsg = "socket_option() failed";
            $this->is_valied = false;
        }
		if(!@socket_bind($this->master,$this->server_address,$this->server_port)){
			$this->errmsg = sprintf('KuoqiWorker warning:socket_bind failed on %s:%s with %s'."\n",
				$this->server_address,
				$this->server_port,
				socket_strerror(socket_last_error())
			);
			$this->is_valied = false;
		}
		//最多将有个积压传入连接排队等待处理。如果连接请求到达时队列已满，则客户端可能会收到一个错误，指示ECONREFUNCE，或者，如果基础协议支持传输，则可能会忽略该请求，以便重试成功。
        //传递给backlogparameter的最大数量在很大程度上取决于底层平台。在Linux上，它很遗憾地被截断为SOMAXCONN。在win32上，如果通过SOMAXCONN，负责套接字的底层服务提供商将把backlog设置为最大合理值。在这个平台上，没有标准的规定来找出实际的积压价值。
		if(!socket_listen($this->master,$this->backlog)){
            $this->errmsg = "socket_listen() failed";
            $this->is_valied = false;
        }
		$this->sockets[] = $this->master;

		//$this->lister->serverConstruct();
	}

    /**
     * 等待子进程中断，防止子进程成为僵尸进程。
     */
	public function __destruct(){
		pcntl_wait($status); //等待子进程中断，防止子进程成为僵尸进程。
	}

    /**
     * 简单打印函数
     * @param string $msg
     * User kuoqi
     * Datetime 2022/3/24 9:25
     * Remark
     */
    function say($msg = ""){
        echo $msg . PHP_EOL;
    }



    /**
     * 调试打印数据
     * @param string $msg
     * User kuoqi
     * Datetime 2022/3/24 9:30
     * Remark
     */
    function debug($msg = ""){
        if ($this->debug){
            echo $msg . PHP_EOL;
        }
    }


    /**
     * 这是一个模板，在所有继承中，请自己实现本方法的具体信息
     * User kuoqi
     * Datetime 2022/3/24 9:48
     * Remark
     */
	function listen(){
        $this->say('webSocket Listen . you need respire this function to your context');
	}

	public function isValied(){
	    return $this->is_valied;
    }

	//关闭当前websocket
	function close(){}

    /**
     * 执行run
     * User kuoqi
     * Datetime 2022/3/31 14:41
     * Remark
     */
    function run(){
	    $this->say('your need do something in socket->');
    }


    /**
     * 封装http请求头
     * @param $req
     * @return array
     * User kuoqi
     * Datetime 2022/3/24 9:31
     * Remark
     */
	function getHeaders($req){
        $r = $h = $o = $key = null;
        if (preg_match("/GET (.*) HTTP/"              ,$req,$match)) { $r = $match[1]; }
        if (preg_match("/Host: (.*)\r\n/"             ,$req,$match)) { $h = $match[1]; }
        if (preg_match("/Origin: (.*)\r\n/"           ,$req,$match)) { $o = $match[1]; }
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$req,$match)) { $key = $match[1]; }
        return array($r, $h, $o, $key);
    }

    /**
     * 基于websocket version 13
     * @param $key
     * @return string
     * User kuoqi
     * Datetime 2022/3/24 9:31
     * Remark
     */
	function calcKey($key){
        //基于websocket version 13
        $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        return $accept;
    }



	//解码
	function decode($buffer) {
        $len = $masks = $data = $decoded = null;
		if(!$buffer){
			//如果发现这个不符合基础规范的话，如何处理
			echo 'debug-1';
			return ;
		}
        $len = ord($buffer[1]) & 127;

        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } 
        else if ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } 
        else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        return $decoded;
    }
	public function charset($message)
    {
		return mb_convert_encoding($message,"GBK",mb_detect_encoding ($message));
	}
	
	//send message,服务端之间的相互通讯
	public function send($client, $msg){
		if(is_array($msg)){
			$msg = json_encode($msg);	
		}
        //$msg = $this->frame($msg);
        return @socket_write($client, $msg, strlen($msg));
    }
	
	//打包给客户端
	public function encode($buffer){
		$len = strlen($buffer);
		if($len<=125)
		{
			return "\x81".chr($len).$buffer;
		}else if($len<=65535){
			return "\x81".chr(126).pack("n", $len).$buffer;
		}else{
			return "\x81".chr(127).pack("xxxxN", $len).$buffer;
		}	
	}
	
	//发送数据给客户端
	//这个是发送给非同类客户端的时候使用，比如发送给不同语言体系，使用这种方式
	public function sendToClient($client, $msg){
		if(is_array($msg)){
			$msg = json_encode($msg);	
		}
        //$msg = $this->frame($msg);
		$msg = $this->encode($msg);
        return @socket_write($client, $msg, strlen($msg));
	}
}